/**
 * @file      : miscellaneous.c
 * @brief     : 杂项函数集合源文件
 * @author    : huenrong (huenrong1028@gmail.com)
 * @date      : 2020-08-20 17:52:52
 *
 * @copyright : Copyright (c) 2020  成都科鸿凌泰自动识别技术有限公司
 *
 */

#include "./miscellaneous.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <ctype.h>
#include <sys/stat.h>
#include <time.h>
#include <sys/time.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define MONTH_PER_YEAR 12   // 一年12月
#define YEAR_MONTH_DAY 20   // 年月日缓存大小
#define HOUR_MINUTES_SEC 20 // 时分秒缓存大小

/**
 * @brief  获取编译时间戳
 * @param  get_timer: 输出参数: 获取到的时间戳
 */
void get_compile_time(char *get_timer)
{
    const char year_month[MONTH_PER_YEAR][4] = {"Jan", "Feb", "Mar", "Apr",
                                                "May", "Jun", "Jul", "Aug",
                                                "Sep", "Oct", "Nov", "Dec"};
    char compile_date[YEAR_MONTH_DAY] = {0};
    char compile_time[HOUR_MINUTES_SEC] = {0};
    uint8_t i = 0;
    char str_month[4] = {0};
    uint32_t year = 0, month = 0, day = 0, hour = 0, minutes = 0, seconds = 0;

    sprintf(compile_date, "%s", __DATE__);
    sprintf(compile_time, "%s", __TIME__);

    sscanf(compile_date, "%s %02d %04d", str_month, &day, &year);
    sscanf(compile_time, "%02d:%02d:%02d", &hour, &minutes, &seconds);

    for (i = 0; i < MONTH_PER_YEAR; ++i)
    {
        if (0 == strncmp(str_month, year_month[i], 3))
        {
            month = i + 1;
            break;
        }
    }

    sprintf(get_timer, "%04d-%02d-%02d %02d:%02d:%02d",
            year, month, day, hour, minutes, seconds);
}

/**
 * @brief  获取系统当前时区
 * @return 当前系统时区
 */
int get_local_time_zone(void)
{
    int time_zone = 0;
    time_t t1 = -1, t2 = -1;
    struct tm *tm_local = NULL, *tm_utc = NULL;

    time(&t1);
    t2 = t1;

    tm_local = localtime(&t1);
    t1 = mktime(tm_local);

    tm_utc = gmtime(&t2);
    t2 = mktime(tm_utc);

    time_zone = ((t1 - t2) / 3600);

    return time_zone;
}

/**
 * @brief  获取系统当前时间字符串(精确到毫秒)
 * @param  time_buf: 输出参数, 获取到的时间串
 * @param  gap_flag: 输入参数, 获取到的时间是否需要间隔
 *                            0: 无间隔: 20180806163000616678
 *                            1: 有间隔: 2018-08-06 16:30:00.616678
 *                            2: 有间隔: 2018-08-06_16-30-00-616678
 */
void get_time_str(char *time_buf, const uint8_t gap_flag)
{
    struct timeval tv = {0};
    struct timezone tz = {0};
    struct tm *p = NULL;

    gettimeofday(&tv, &tz);
    p = localtime(&tv.tv_sec);

    if (0 == gap_flag)
    {
        sprintf(time_buf, "%4d%02d%02d%02d%02d%02d%06ld",
                1900 + (p->tm_year), 1 + (p->tm_mon), p->tm_mday,
                p->tm_hour, p->tm_min, p->tm_sec, tv.tv_usec);
    }
    else if (1 == gap_flag)
    {
        sprintf(time_buf, "%4d-%02d-%02d %02d:%02d:%02d.%06ld",
                1900 + (p->tm_year), 1 + (p->tm_mon), p->tm_mday,
                p->tm_hour, p->tm_min, p->tm_sec, tv.tv_usec);
    }
    else if (2 == gap_flag)
    {
        sprintf(time_buf, "%4d-%02d-%02d_%02d-%02d-%02d-%06ld",
                1900 + (p->tm_year), 1 + (p->tm_mon), p->tm_mday,
                p->tm_hour, p->tm_min, p->tm_sec, tv.tv_usec);
    }
}

/**
 * @brief  获取系统当前时间
 * @param  local_time: 输出参数, 获取到的本地时间
 */
void get_local_time(struct date_time *local_time)
{
    struct timeval tv = {0};
    struct timezone tz = {0};
    struct tm *p = NULL;

    gettimeofday(&tv, &tz);
    p = localtime(&tv.tv_sec);

    local_time->year = (p->tm_year + 1900 - 2000);
    local_time->month = ((p->tm_mon) + 1);
    local_time->day = p->tm_mday;
    local_time->hour = p->tm_hour;
    local_time->minute = p->tm_min;
    local_time->second = p->tm_sec;
}

/**
 * @brief  设置系统当前时间
 * @param  year: 输入参数, 待设置年, 4位年份值, 例: 1995
 * @param  mon : 输入参数, 待设置月, 取值范围: [1, 12]
 * @param  mday: 输入参数, 待设置日, 取值范围: [1, 31]
 * @param  hour: 输入参数, 待设置时, 取值范围: [0, 23]
 * @param  min : 输入参数, 待设置分, 取值范围: [0, 59]
 * @param  sec : 输入参数, 待设置秒, 取值范围: [0, 60]
 * @return 成功: 0
 *         失败: errno错误码
 */
int set_local_time(const uint16_t year, const uint8_t mon, const uint8_t mday,
                   const uint8_t hour, const uint8_t min, const uint8_t sec)
{
    int ret = -1;
    time_t t = -1;
    struct tm new_time = {0};

    new_time.tm_sec = sec;
    new_time.tm_min = min;
    new_time.tm_hour = hour;
    new_time.tm_mday = mday;
    new_time.tm_mon = (mon - 1);
    new_time.tm_year = (year - 1900);
    new_time.tm_isdst = -1;

    t = mktime(&new_time);
    ret = stime(&t);

    return ret;
}

/**
 * @brief  修改指定文件内容的一整行
 * @param  file_path: 输入参数, 文件路径
 * @param  num      : 输入参数, 需要修改的内容在文件中第几次出现(0: 修改所有)
 * @param  old_str  : 输入参数, 待修改的原内容
 * @param  new_str  : 输入参数, 修改后的内容(如果是一行的结尾需要手动加\n)
 * @return 成功: 0
 *         失败: -1
 */
int modify_file(const char *file_path, const uint8_t num,
                const char *old_str,
                const char *new_str)
{
    FILE *modifile;
    int flen;
    struct stat s;
    char line[1024], *buf, *tmp;
    uint8_t temp = 0; // 目标字符串出现的次数

    if (lstat(file_path, &s) < 0)
    {
        return -1;
    }

    flen = s.st_size;                 // 文件长度
    buf = (char *)malloc(flen + 100); // 预留100个字节可能增加文件长度

    if (!buf)
    {
        return -1;
    }

    memset(buf, 0, flen + 100);
    tmp = buf; // 文件小的时候可以不用tmp, 直接strcat拼接字符串
    modifile = fopen(file_path, "r");

    if (!modifile)
    {
        return -1;
    }

    while (!feof(modifile))
    {
        if (!fgets(line, 1024, modifile))
        {
            continue;
        }

        if (!strstr(line, old_str))
        {
            strcpy(tmp, line);
            tmp += strlen(line);
        }
        else
        {
            temp++;
            if ((0 != num) && (num == temp))
            {
                sprintf(tmp, new_str);
                tmp += strlen(new_str);
            }
        }
    }

    fclose(modifile);
    modifile = fopen(file_path, "w+");

    if (!modifile)
    {
        return -1;
    }

    fprintf(modifile, "%s", buf);
    free(buf);
    fclose(modifile);

    return 0;
}

/**
 * @brief  获取本地ip地址
 * @param  ip_str: 输出参数, 获取到的ip地址
 * @param  ifname: 输入参数, 网卡名字(如: eth0, eth1...)
 * @return 成功: 0
 *         失败: -1
 */
int get_local_ip(char *ip_str, const char *ifname)
{
    int sock_get_ip = -1;
    struct sockaddr_in *sin;
    struct ifreq ifr_ip;

    sock_get_ip = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == sock_get_ip)
    {
        perror("get_local_ip, socket create fail");

        return -1;
    }

    memset(&ifr_ip, 0, sizeof(ifr_ip));
    strncpy(ifr_ip.ifr_name, ifname, sizeof(ifr_ip.ifr_name) - 1);

    if (ioctl(sock_get_ip, SIOCGIFADDR, &ifr_ip) < 0)
    {
        return -1;
    }
    sin = (struct sockaddr_in *)&ifr_ip.ifr_addr;
    strcpy(ip_str, inet_ntoa(sin->sin_addr));

    close(sock_get_ip);

    return 0;
}

/**
 * @brief  设置本地ip地址
 * @param  ip_str: 输入参数, 待设置ip地址
 * @param  ifname: 输入参数, 待设置网卡名字(如: eth0, eth1...)
 * @return 成功: 0
 *         失败: -1
 */
int set_local_ip(const char *ip_str, const char *ifname)
{
    int ret = -1;
    char new_ip[128] = {0};

    snprintf(new_ip, 128, "\taddress %s\n", ip_str);
    ret = modify_file("/etc/network/interfaces", 1, "address ", new_ip);

    return ret;
}

/**
 * @brief  获取本地子网掩码
 * @param  netmask_str: 输出参数, 获取到的子网掩码
 * @param  ifname     : 输入参数, 网卡名字(如: eth0, eth1...)
 * @return 成功: 0
 *         失败: -1
 */
int get_local_netmask(char *netmask_str, const char *ifname)
{
    int sock_netmask;
    struct ifreq ifr_mask;
    struct sockaddr_in *net_mask;

    sock_netmask = socket(AF_INET, SOCK_STREAM, 0);
    if (sock_netmask == -1)
    {
        perror("get_local_netmask, create socket fail");
        return -1;
    }

    memset(&ifr_mask, 0, sizeof(ifr_mask));
    strncpy(ifr_mask.ifr_name, ifname, sizeof(ifr_mask.ifr_name) - 1);

    if ((ioctl(sock_netmask, SIOCGIFNETMASK, &ifr_mask)) < 0)
    {
        printf("mac ioctl error\n");

        return -1;
    }

    net_mask = (struct sockaddr_in *)&(ifr_mask.ifr_netmask);
    strcpy(netmask_str, inet_ntoa(net_mask->sin_addr));

    close(sock_netmask);

    return 0;
}

/**
 * @brief  设置本地子网掩码
 * @param  netmask_str: 输入参数, 待设置子网掩码
 * @param  ifname     : 输入参数, 待设置网卡名字(如: eth0, eth1...)
 * @return 成功: 0
 *         失败: -1
 */
int set_local_netmask(const char *netmask_str, const char *ifname)
{
    int ret = -1;
    char new_netmask[128] = {0};

    snprintf(new_netmask, 128, "\tnetmask %s\n", netmask_str);
    ret = modify_file("/etc/network/interfaces", 1, "netmask ", new_netmask);

    return ret;
}

/**
 * @brief  获取本机默认网关
 * @param  gateway_addr_str: 输出参数, 获取到的默认网关
 * @return 成功: 0
 *         失败: -1
 */
int get_local_gateway_addr(char *gateway_addr_str)
{
    char buff[256];
    int nl = 0;
    struct in_addr gw;
    int flgs, ref, use, metric;
    unsigned long int d, g, m;

    FILE *fp = NULL;

    fp = fopen("/proc/net/route", "r");
    if (fp == NULL)
    {
        return -1;
    }

    nl = 0;
    memset(buff, 0, sizeof(buff));
    while (fgets(buff, sizeof(buff), fp) != NULL)
    {
        if (nl)
        {
            int ifl = 0;
            while (buff[ifl] != ' ' && buff[ifl] != '\t' && buff[ifl] != '\0')
                ifl++;
            buff[ifl] = 0;
            if (sscanf(buff + ifl + 1, "%lx%lx%X%d%d%d%lx",
                       &d, &g, &flgs, &ref, &use, &metric, &m) != 7)
            {
                fclose(fp);
                return -2;
            }

            ifl = 0;

            gw.s_addr = g;

            if (d == 0)
            {
                strcpy(gateway_addr_str, inet_ntoa(gw));
                fclose(fp);
                return 0;
            }
        }
        nl++;
    }

    if (fp)
    {
        fclose(fp);
        fp = NULL;
    }

    return -1;
}

/**
 * @brief  设置本机默认网关
 * @param  gateway_addr_str: 输入参数, 待设置的默认网关
 * @return 成功: 0
 *         失败: -1
 */
int set_local_gateway_addr(const char *gateway_addr_str)
{
    int ret = -1;
    char new_gateway_addr_str[128] = {0};

    snprintf(new_gateway_addr_str, 128, "\tgateway %s\n", gateway_addr_str);
    ret = modify_file("/etc/network/interfaces", 1, "gateway ",
                      new_gateway_addr_str);

    return ret;
}

/**
 * @brief  把16进制转换为ASCII字符
 * @param  hex: 输入参数, 需要转换的16进制
 * @return 转换后的ASCII字符
 */
uint8_t hex_to_ascii(const uint8_t hex)
{
    uint8_t ascii = 0xFF;

    // 数字
    if ((hex >= 0x30) && (hex <= 0x39))
    {
        ascii = (hex - 0x30);
    }
    // 大写字母
    else if ((hex >= 0x41) && (hex <= 0x46))
    {
        ascii = (hex - 0x37);
    }
    // 小写字母
    else if ((hex >= 0x61) && (hex <= 0x66))
    {
        ascii = (hex - 0x57);
    }
    else
    {
        ascii = 0xFF;
    }

    return ascii;
}

/**
 * @brief  ASCII码转hex字符(0~9, A~F的ASCII码还原成0~15)
 * @param  ascii: 输入参数, 需要转换的ASCII字符
 * @return 转换完成的hex字符
 */
uint8_t ascii_to_hex(const uint8_t ascii)
{
    uint8_t hex_data = 0xFF;

    if (ascii <= '9')
    {
        hex_data = (ascii - '0');
    }
    else
    {
        hex_data = (ascii - ('A' - 10));
    }

    return hex_data;
}

/**
 * @brief  十六进制字符转换成普通字符
 * @param  str    : 输出参数, 转换后的字符
 * @param  hex    : 输入参数, 要转换的字符
 * @param  hex_len: 输入参数, 要转换的字符长度
 * @return 转换后的字符长度
 */
uint32_t hex_to_str(uint8_t *str, const uint8_t *hex, const uint32_t hex_len)
{
    uint32_t i = 0;
    uint8_t ch, ch1;
    uint32_t len = 0;

    for (i = 0; i < hex_len; i++)
    {
        ch = *hex++;
        ch1 = (ch & 0xF0) >> 4;
        if (ch1 > 9)
        {
            ch1 = ch1 - 10 + 'A';
        }
        else
        {
            ch1 += '0';
        }

        *str++ = ch1;
        len++;

        ch1 = (ch & 0x0F);
        if (ch1 > 9)
        {
            ch1 = ch1 - 10 + 'A';
        }
        else
        {
            ch1 += '0';
        }
        *str++ = ch1;
        *str++ = ' ';
        len += 2;
    }
    *str = '\0';
    len++;

    return len;
}

/**
 * @brief  十六进制字符串转换为字节流
 * @param  dest      : 输出参数, 转换后的字节流
 * @param  source    : 输入参数, 要转换的字符串
 * @param  source_len: 输入参数, 需要转换的字符串的长度
 */
void hex_str_to_byte(uint8_t *dest, const char *source, const int source_len)
{
    short i;
    uint8_t high_byte, low_byte;

    for (i = 0; i < source_len; i += 2)
    {
        high_byte = toupper(source[i]);
        low_byte = toupper(source[i + 1]);

        if (high_byte > 0x39)
        {
            high_byte -= 0x37;
        }
        else
        {
            high_byte -= 0x30;
        }

        if (low_byte > 0x39)
        {
            low_byte -= 0x37;
        }
        else
        {
            low_byte -= 0x30;
        }

        dest[i / 2] = ((high_byte << 4) | low_byte);
    }
}

/**
 * @brief  根据unix时间戳计算本地时间
 * @param  local_time: 输出参数, 获取到的本地时间
 * @param  unix_time : 输入参数, unix时间戳
 */
void unix_time_to_local_time(struct date_time *local_time,
                             const uint64_t unix_time)
{
    struct tm *time = NULL;

    // 获取当地时间
    time = localtime((const time_t *)&unix_time);

    local_time->year = (time->tm_year + 1900);
    local_time->month = (time->tm_mon + 1);
    local_time->day = time->tm_mday;
    local_time->hour = time->tm_hour;
    local_time->minute = time->tm_min;
    local_time->second = time->tm_sec;
}

/**
 * @brief  根据本地时间计算unix时间戳
 * @param  unix_time : 输出参数, 获取到的unix时间戳
 * @param  local_time: 输入参数, 本地时间
 */
void local_time_to_unix_time(uint32_t *unix_time,
                             const struct date_time local_time)
{
    struct tm time = {0};

    time.tm_year = (local_time.year - 1900);
    time.tm_mon = (local_time.month - 1);
    time.tm_mday = local_time.day;
    time.tm_hour = local_time.hour;
    time.tm_min = local_time.minute;
    time.tm_sec = local_time.second;

    *unix_time = mktime(&time);
}

/**
 * @brief  目标数据查找
 * @param  source_data         : 输入参数, 源数据
 * @param  source_data_len     : 输入参数, 源数据长度
 * @param  destination_data    : 输入参数, 目标数据
 * @param  destination_data_len: 输入参数, 目标数据长度
 * @return 成功: 返回目标字符串第一个字符的位置
 *         失败: 返回-1
 */
int destination_data_lookup(const uint8_t *source_data,
                            const uint16_t source_data_len,
                            const uint8_t *destination_data,
                            const uint8_t destination_data_len)
{
    uint16_t i = 0;

    // 数据格式不合法, 直接退出
    if (source_data_len < destination_data_len)
    {
        return -1;
    }

    // 循环查找目标字符串
    for (i = 0; i < source_data_len; i++)
    {
        // 第一个字符相等
        if (source_data[i] == destination_data[0])
        {
            // 剩余的数据长度大于正确的数据长度
            if ((source_data_len - i) >= destination_data_len)
            {
                if (memcmp(&source_data[i], destination_data, destination_data_len) == 0)
                {
                    return i;
                }
            }
        }
    }

    return -1;
}

/**
 * @brief  大小端交换
 * @param  source_data: 输入参数, 源数据
 * @return 交换后的数据
 */
uint32_t byte_swap_32(const uint32_t source_data)
{
    return (((source_data & 0xFF000000) >> 24) |
            ((source_data & 0x00FF0000) >> 8) |
            ((source_data & 0x0000FF00) << 8) |
            ((source_data & 0x000000FF) << 24));
}

/**
 * @brief  执行命令并获取输出结果
 * @param  result: 输出参数, 命令输出结果
 * @param  cmd   : 输入参数, 待执行命令
 * @return 成功: 返回0
 *         失败: 返回-1
 */
int execute_cmd(char *result, const char *cmd)
{
    char result_buf[1024] = {0}; // 执行结果
    FILE *ptr = NULL;

    ptr = popen(cmd, "r");
    if (NULL != ptr)
    {
        // 循环获取返回值, 一行一行的获取
        while (NULL != fgets(result_buf, 1024, ptr))
        {
            // 将当前行数据, 追加到目标buf
            strcat(result, result_buf);
            // 获取结果长度过长, 返回错误
            if (strlen(result) > 1024)
            {
                pclose(ptr);
                ptr = NULL;

                return -1;
            }
        }

        pclose(ptr);
        ptr = NULL;

        return 0;
    }

    return -1;
}

/**
 * @brief  清空socket缓存区
 * @param  fd: 输入参数, 文件描述符
 * @return 成功: 0
 *         失败: -1
 */
int clear_socket_cache(const int fd)
{
    struct timeval time_out;
    int ret = -1;
    fd_set fds;
    char read_data[2] = {0};

    // 超时时间设置为0, select不等待, 直接返回
    time_out.tv_sec = 0;
    time_out.tv_usec = 0;

    FD_ZERO(&fds);    // 描述符集合置0
    FD_SET(fd, &fds); // 设置需要监听的文件描述符

    memset(read_data, 0, sizeof(read_data));
    while (1)
    {
        ret = select((fd + 1), &fds, NULL, NULL, &time_out);
        // 返回负值, 表示缓select发生错误
        if (ret < 0)
        {
            return -1;
        }
        // 返回0, 表示缓冲区中已经没有数据
        else if (0 == ret)
        {
            break;
        }
        // 从缓冲区读取1字节数据
        recv(fd, read_data, 1, 0);
    }

    return 0;
}

/**
 * @brief  获取文件大小
 * @param  file_path: 输入参数, 文件路径
 * @return 成功: 返回文件大小(单位: bytes)
 *         失败: -1
 */
long int get_file_size(const char *file_path)
{
    int ret = -1;
    long int file_size = -1;
    struct stat stat_buf = {0};

    ret = stat(file_path, &stat_buf);
    if (0 != ret)
    {
        return -1;
    }

    file_size = stat_buf.st_size;

    return file_size;
}

/**
 * @brief  计算文件md5校验
 * @param  file_md5 : 输出参数, 获取到的文件md5校验
 * @param  file_path: 输入参数, 文件路径
 * @return 成功: 0
 *         失败: -1
 */
int get_file_md5(char *file_md5, const char *file_path)
{
    int ret = -1;
    char cmd[256] = {0};

    sprintf(cmd, "md5sum %s | cut -d \" \" -f1", file_path);
    ret = execute_cmd(file_md5, cmd);

    return ret;
}
